package com.gsbelarus.gedemin.lib.sync.protocol;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import com.gsbelarus.gedemin.lib.sync.protocol.table.ParsTable;

import java.text.SimpleDateFormat;

/**
 * Модель класса для работы с базой данных.
 * Управляет редактированием базы данных, содержит методы insert, update, reset, delete.
 * Создает таблицу MB_SYNC для хранения информации об обновлении
 */
abstract public class DataBaseModel {

    protected Context context;
    protected SQLiteDatabase mDB = null;
    protected DBHelper mDBHelper = null;

    protected boolean isTransaction = false;

    protected Long syncDateInMilliSeconds = null;
    protected Integer verData = null;

    protected static final String DATABASE_NAME = "database.db";

    public static final String TABLE_SYNC_NAME = "MB_SYNC";
    public static final String UID = "ID";
    public static final String VER = "VER";
    public static final String VERDATE = "VERDATE";
    public static final String VERSYNC = "VERSYNC";

    private static final String MB_SYNC_CREATE = "CREATE TABLE IF NOT EXISTS "
            + TABLE_SYNC_NAME + " ("
            + UID + " INTEGER PRIMARY KEY,"
            + VER + " INTEGER,"
            + VERDATE + " DATETIME,"
            + VERSYNC + " DATETIME"
            + ");";

    private static final String MB_SYNC_DELETE = "DROP TABLE IF EXISTS "
            + TABLE_SYNC_NAME;

    /**
     * @return номер версии базы данных.
     * Если версия данных больше текущей, вызовится метод onUpgradeDB
     */
    abstract protected int getDataBaseVer();

    /**
     * Вызывается при создании базы данных. Внутри следует создать все таблицы
     *
     * @param sqLiteDatabase объект для работы с базой данных
     */
    abstract protected void onCreateDB(SQLiteDatabase sqLiteDatabase);

    /**
     * Вызывается после вызова метода recreateDataBase. Внутри следует удалить все таблицы
     *
     * @param sqLiteDatabase объект для работы с базой данных
     */
    abstract protected void onDeleteDB(SQLiteDatabase sqLiteDatabase);

    /**
     * Вызывается при несовпадении версий базы данных
     *
     * @param sqLiteDatabase объект для работы с базой данных
     * @param oldVer         старая версия базы данных
     * @param newVer         новая версия базы данных
     */
    abstract protected void onUpgradeDB(SQLiteDatabase sqLiteDatabase, int oldVer, int newVer);

    /**
     * Заполнение таблиц случайными данными
     */
    abstract protected void createDemoDB();

    protected DataBaseModel(Context context) {
        mDBHelper = new DBHelper(context, DATABASE_NAME, null, getDataBaseVer());
        this.context = context;
    }

    private class DBHelper extends SQLiteOpenHelper {

        public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }

        @Override
        public void onCreate(SQLiteDatabase sqLiteDatabase) {
            sqLiteDatabase.execSQL(MB_SYNC_CREATE);
            onCreateDB(sqLiteDatabase);
        }

        public void deleteDB(SQLiteDatabase sqLiteDatabase) {
            sqLiteDatabase.execSQL(MB_SYNC_DELETE);
            onDeleteDB(sqLiteDatabase);
        }

        @Override
        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
            onUpgradeDB(sqLiteDatabase, oldVersion, newVersion);
        }
    }

    /**
     * открыть базу данных для чтения и записи
     */
    protected void open() {
        //If connection to db is not open then will open connection
        if ((mDB == null) || (!mDB.isOpen())) {
            mDB = mDBHelper.getWritableDatabase();
        }
    }

    /**
     * закрыть базу дынных
     */
    public void close() {
        mDB.close();
    }

    /**
     * Заполняет таблицу MB_SYNC демо данными, после чего вызывает createDemoDB()
     */
    public final void createDemo() {
        recreateDataBase();
        ContentValues cv = new ContentValues();
        cv.put(UID, 0);
        cv.put(VER, 0);
        cv.put(VERDATE, new SimpleDateFormat("yyyy-MM-dd").format(System.currentTimeMillis()));
        cv.put(VERSYNC, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));

        ParsTable table = new ParsTable();
        table.setTable(TABLE_SYNC_NAME);
        table.setContentValues(cv);
        insert(table);

        createDemoDB();
    }

    /**
     * Удалить таблицу
     *
     * @param parsTables должно содержать поле table
     */
    public void resetTable(ParsTable parsTables) {
        mDB.delete(parsTables.getTable(), null, null);
    }

    /**
     * Удалить поля по UID
     *
     * @param parsTables должно содержать поле table и values
     */
    public void delete(ParsTable parsTables) {
        for (int i = 0; i < parsTables.getValues().length; i++) {
            mDB.delete(parsTables.getTable(), UID + "=" + parsTables.getValues()[i], null);
        }
    }

    /**
     * Обновить запись с заданным UID. Если UID не найден, то создать запись с таким UID
     *
     * @param parsTables должно содержать поле table и contentValues
     */
    public void update(ParsTable parsTables) {     // проверить
        if (mDB.update(parsTables.getTable(), parsTables.getContentValues(),
                UID + "=" + parsTables.getContentValues().getAsString(UID), null) == 0)
            insert(parsTables);
    }

    /**
     * Создать запись
     *
     * @param parsTables должно содержать поле table и contentValues
     */
    public void insert(ParsTable parsTables) {  // проверить
        mDB.insert(parsTables.getTable(), null, parsTables.getContentValues());
    }

    public void beginTransaction() {
        isTransaction = true;
        mDB.beginTransaction();
    }

    public void transactionSuccessful() {
        mDB.setTransactionSuccessful();
    }

    public void endTransaction() {
        mDB.endTransaction();
        isTransaction = false;
        initTempVar();
    }

    /**
     * Удаляет всю базу данных и создают пустую
     */
    public void recreateDataBase() {
        mDBHelper.deleteDB(mDB);
        createDataBase();
    }

    /**
     * Создает таблицы если еще не созданы, инициализирует временные переменные через метод initTempVar()
     */
    public void createDataBase() {
        mDBHelper.onCreate(mDB);
        initTempVar();
    }

    /**
     * пересоздание временных переменных для даты синхронизации, версии данных
     */
    protected void initTempVar() {
        syncDateInMilliSeconds = null;
        verData = null;
        getSyncDateInMilliSeconds();
        getVerData();
    }

    /**
     * @return дата синзронизации.
     * Проверяет временную переменную, если null делает запрос, если не null возвращает временную переменную
     * временная переменная чистится при закрытии транзакции и при очистке базы
     */
    public Long getSyncDateInMilliSeconds() {
        if (syncDateInMilliSeconds != null)
            return syncDateInMilliSeconds;

        String sql = "select strftime('%s', " + VERSYNC + ", 'utc') * 1000 " +
                "from " + TABLE_SYNC_NAME;

        if (isTransaction) return null;

        Cursor cursor = mDB.rawQuery(sql, null);
        cursor.moveToFirst();

        if (cursor.getCount() == 0 || cursor.getLong(0) == 0)
            return (syncDateInMilliSeconds = null);

        return (syncDateInMilliSeconds = cursor.getLong(0));
    }

    /**
     * @return версия данных.
     * Проверяет временную переменную, если null делает запрос, если не null возвращает временную переменную
     * временная переменная чистится при закрытии транзакции и при очистке базы
     */
    public int getVerData() {
        if (verData != null)
            return verData;

        String sql = "select " + VER + " " +
                "from " + TABLE_SYNC_NAME;

        Cursor cursor = mDB.rawQuery(sql, null);
        cursor.moveToFirst();

        if (cursor.getCount() == 0 || cursor.getInt(0) == 0) {
            verData = null;
            return 0;
        }

        return (verData = cursor.getInt(0));
    }
}
